package jmine.tec.subscription.replicator;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import jmine.tec.subscription.Publication;
import jmine.tec.subscription.Publisher;
import jmine.tec.subscription.SubscriptionManager;
import jmine.tec.subscription.SubscriptionSettings;
import jmine.tec.subscription.impl.PartialPublication;
import jmine.tec.subscription.impl.SubscriptionManagerImplementor;
import jmine.tec.utils.io.IOUtils;
import jmine.tec.utils.register.Receiver;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;

/**
 * @author takeshi
 */
public final class SingletonPublicationReplicator implements PublicationReplicator, Receiver<SubscriptionManagerImplementor>,
        BeanFactoryAware, InitializingBean {

    private ListableBeanFactory listableBeanFactory;

    private final List<SubscriptionManagerImplementor> implementor = new CopyOnWriteArrayList<SubscriptionManagerImplementor>();

    /**
     * 
     */
    private SingletonPublicationReplicator() {
    }

    /**
     * Private inner class to hold singleton instance
     * 
     * @author takeshi
     */
    private static final class InstanceHolder {
        private static final SingletonPublicationReplicator INSTANCE = new SingletonPublicationReplicator();

        /**
         * 
         */
        private InstanceHolder() {
        }
    }

    /**
     * @return the single instance
     */
    public static SingletonPublicationReplicator getInstance() {
        return InstanceHolder.INSTANCE;
    }

    /**
     * 
     */
    public void clear() {
        this.implementor.clear();
    }

    /**
     * {@inheritDoc}
     */
    public void setSubscriptionManager(SubscriptionManagerImplementor smi) {
        if (!this.implementor.contains(smi)) {
            this.implementor.add(smi);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void register(Collection<? extends SubscriptionManagerImplementor> registers) {
        this.implementor.addAll(registers);
    }

    /**
     * {@inheritDoc}
     */
    public void setBeanFactory(BeanFactory beanFactory) {
        if (beanFactory instanceof ListableBeanFactory) {
            this.listableBeanFactory = (ListableBeanFactory) beanFactory;
        }
    }

    /**
     * {@inheritDoc}
     */
    public <P extends Publication> void synchronizePartialPublication(Publisher<P> publisher, SubscriptionSettings settings,
            PartialPublication<P> publication, SubscriptionManager source) {
        PartialPublication<P> clone = IOUtils.deepClone(publication);
        for (SubscriptionManagerImplementor sm : this.implementor) {
            if (!source.equals(sm)) {
                sm.localPublishPartialPublication(publisher, settings, clone);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public <P extends Publication> void synchronizePublication(Publisher<P> publisher, SubscriptionSettings settings, P publication,
            SubscriptionManager source) {
        P clone = IOUtils.deepClone(publication);
        for (SubscriptionManagerImplementor sm : this.implementor) {
            if (!source.equals(sm)) {
                // P other = (P) IOUtils.deepClone(publication);
                sm.localPublish(publisher, settings, clone);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void afterPropertiesSet() {
        if (this.listableBeanFactory != null) {
            String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(listableBeanFactory, SubscriptionManagerImplementor.class);
            List<SubscriptionManagerImplementor> list = new ArrayList<SubscriptionManagerImplementor>(names.length);
            for (String string : names) {
                SubscriptionManagerImplementor s =
                        (SubscriptionManagerImplementor) this.listableBeanFactory.getBean(string, SubscriptionManagerImplementor.class);
                list.add(s);
            }
            this.register(list);
        }
    }

}
